package game_logic.control;

import game_logic.model.Board;
import game_logic.model.Piece;
import game_logic.util.Vec2;

import java.util.ArrayList;
import java.util.List;


/**
 * 移动控制器: 处理棋子的移动逻辑，验证合法的移动，并更新模型。
 */
public class MoveController {

    /**
     * 将位置 S 的棋子移动到位置 T，如果不合法则不作任何操作，返回是否移动成功。
     * 注意：函数假设坐标均是合法坐标
     * 另外，以下行为会抛出异常：
     * 位置 S 处没有棋子 UnsupportedOperationException
     *
     * @param board 棋盘
     * @param src   位置 S
     * @param tar   位置 T
     * @return 是否移动成功
     */
    public boolean moveIfLegal(Board board, Vec2 src, Vec2 tar) {
        List<Vec2> movable = this.getMovablePosition(board, src);
        if (!movable.contains(tar)) return false;
        board.setPiece(tar.x, tar.y, board.getPiece(src.x, src.y));
        board.setPiece(src.x, src.y, null);
        return true;
    }


    /**
     * 返回 src 处的棋子能移动到的节点。如果 src 处没有棋子，那么抛出异常 IllegalArgumentException
     *
     * @param board 棋盘
     * @param src   开始位置
     * @return 可移动到的位置
     */
    public List<Vec2> getMovablePosition(Board board, Vec2 src) {
        if (board.getPiece(src.x, src.y) == null) throw new IllegalArgumentException("src 处没有棋子");
        switch (board.getPiece(src.x, src.y).getType()) {
            case Chariot:
                return this.getMovablePositionChariot(board, src); // 车
            case Horse:
                return this.getMovablePositionHorse(board, src); // 马
            case Cannon:
                return this.getMovablePositionCannon(board, src); // 炮
            case Soldier:
                return this.getMovablePositionSoldier(board, src); // 兵
            case Elephant:
                return this.getMovablePositionElephant(board, src); // 象
            case Guard:
                return this.getMovablePositionGuard(board, src); // 士
            case King:
                return this.getMovablePositionKing(board, src); // 帅
            default:
                throw new UnsupportedOperationException("getMovablePosition, 出现未知异常，棋子类型无法识别");
        }
    }

    private final Vec2[] dv = new Vec2[]{new Vec2(1, 0), // 右0
            new Vec2(2, 1), // 日字1
            new Vec2(1, 1), // 右上2
            new Vec2(1, 2), // 日字3
            new Vec2(0, 1), // 上4
            new Vec2(-1, 2), // 日字5
            new Vec2(-1, 1), // 左上6
            new Vec2(-2, 1), // 日字7
            new Vec2(-1, 0), // 左8
            new Vec2(-2, -1), // 日字9
            new Vec2(-1, -1), // 左下10
            new Vec2(-1, -2), // 日字11
            new Vec2(0, -1), // 下12
            new Vec2(1, -2), // 日字13
            new Vec2(1, -1), // 右下14
            new Vec2(2, -1), // 日字15
    };

    // 将、士、兵判断红黑方
    protected List<Vec2> getMovablePositionKing(Board board, Vec2 src) {
        List<Vec2> ret = new ArrayList<>();
        int owner = board.getPiece(src.x, src.y).getOwner();
        for (int i = 0; i < 16; i += 4) {
            Vec2 tar = Vec2.add(src, dv[i]);
            if (owner == 0 && this.isInRedPalace(tar)) this.addIfNoOcc(ret, board, src, tar);
            if (owner == 1 && this.isInBlackPalace(tar)) this.addIfNoOcc(ret, board, src, tar);
        }
        return ret;
    }

    protected List<Vec2> getMovablePositionGuard(Board board, Vec2 src) {
        List<Vec2> ret = new ArrayList<>();
        int owner = board.getPiece(src.x, src.y).getOwner();
        for (int i = 2; i < 16; i += 4) { // 遍历对角线
            Vec2 tar = Vec2.add(src, dv[i]);
            if (owner == 0 && this.isInRedPalace(tar)) this.addIfNoOcc(ret, board, src, tar);
            if (owner == 1 && this.isInBlackPalace(tar)) this.addIfNoOcc(ret, board, src, tar);
        }
        return ret;

    }

    protected List<Vec2> getMovablePositionSoldier(Board board, Vec2 src) {
        List<Vec2> ret = new ArrayList<>();
        int owner = board.getPiece(src.x, src.y).getOwner();
        if (owner == 0) this.addIfNoOcc(ret, board, src, Vec2.add(src, dv[4])); // 红方，向上
        if (owner == 1) this.addIfNoOcc(ret, board, src, Vec2.add(src, dv[12])); // 黑方，向下
        if (owner == 0 && this.isInBlack(src) || owner == 1 && this.isInRed(src)) { // 过河兵
            this.addIfNoOcc(ret, board, src, Vec2.add(src, dv[8])); // 左
            this.addIfNoOcc(ret, board, src, Vec2.add(src, dv[0])); // 右
        }
        return ret;
    }

    // 马、象判断蹩脚

    protected List<Vec2> getMovablePositionElephant(Board board, Vec2 src) {
        List<Vec2> ret = new ArrayList<>();
        int owner = board.getPiece(src.x, src.y).getOwner();
        for (int i = 2; i < 16; i += 4) { // 遍历对角线
            Vec2 tmp = Vec2.add(src, dv[i]); // 判断蹩脚
            if (!this.isInside(tmp)) continue;
            if (board.getPiece(tmp.x, tmp.y) != null) continue; // 塞象心
            Vec2 tar = Vec2.add(tmp, dv[i]); // 目的地
            if (owner == 0 && this.isInRed(tar)) this.addIfNoOcc(ret, board, src, tar);
            if (owner == 1 && this.isInBlack(tar)) this.addIfNoOcc(ret, board, src, tar);
        }
        return ret;
    }

    protected List<Vec2> getMovablePositionHorse(Board board, Vec2 src) {
        List<Vec2> ret = new ArrayList<>();
        for (int i = 0; i < 16; i += 4) { // 遍历上下左右
            Vec2 tmp = Vec2.add(src, dv[i]); // 判断蹩脚
            if (!this.isInside(tmp)) continue;
            if (board.getPiece(tmp.x, tmp.y) != null) continue; // 蹩马腿
            this.addIfNoOcc(ret, board, src, Vec2.add(src, dv[(i + 1 + 16) % 16])); // 前进方向的左侧
            this.addIfNoOcc(ret, board, src, Vec2.add(src, dv[(i - 1 + 16) % 16])); // 前进方向的右侧
        }
        return ret;
    }

    // 车、炮判断路径阻碍的棋子数量
    protected List<Vec2> getMovablePositionChariot(Board board, Vec2 src) {
        List<Vec2> ret = new ArrayList<>();
        for (int i = 0; i < 16; i += 4) { // 遍历上下左右
            for (Vec2 tar = Vec2.add(src, dv[i]); this.isInside(tar); tar = Vec2.add(tar, dv[i])) { // 逐步移动当前位置
                this.addIfNoOcc(ret, board, src, tar); // 尝试放入
                if (board.getPiece(tar.x, tar.y) != null) break; // 遇到棋子即退出。addIfNoOcc 已处理不同棋子类型的差异
            }
        }
        return ret;
    }

    protected List<Vec2> getMovablePositionCannon(Board board, Vec2 src) {
        List<Vec2> ret = new ArrayList<>();
        for (int i = 0; i < 16; i += 4) { // 遍历上下左右
            for (Vec2 tar = Vec2.add(src, dv[i]); this.isInside(tar); tar = Vec2.add(tar, dv[i])) { // 逐步移动当前位置
                if (board.getPiece(tar.x, tar.y) == null) { // 没有遇到棋子
                    this.addIfNoOcc(ret, board, src, tar); // 尝试放入
                } else { // 如果遇到棋子，那么当做炮架，
                    // 寻找后面的下一个棋子
                    for (tar = Vec2.add(tar, dv[i]); this.isInside(tar); tar = Vec2.add(tar, dv[i])) { // 逐步移动当前位置，直至遇到下一个棋子
                        if (board.getPiece(tar.x, tar.y) != null) break; // 遇到棋子即退出
                    }
                    if (this.isInside(tar)) this.addIfNoOcc(ret, board, src, tar); // 找到了，那么尝试放入
                    break;
                }
            }
        }
        return ret;
    }

    /**
     * 返回特定棋子
     *
     * @param board
     * @param piece
     * @return Vec2 对象的坐标
     */
    public Vec2 getPiecePos(Board board, Piece piece) {
        for (int x = 0; x < 9; ++x) {
            for (int y = 0; y < 10; ++y) {
                if (board.getPiece(x, y) == piece) return new Vec2(x, y);
            }
        }
        return new Vec2(-1, -1);
    }

    // 辅助函数，棋盘：0-8 * 0-9

    /**
     * @return 是否在红方九宫格
     */
    private boolean isInRedPalace(Vec2 vec) {
        return 3 <= vec.x && vec.x <= 5 && 0 <= vec.y && vec.y <= 2;
    }

    /**
     * @return 是否在黑方九宫格
     */
    private boolean isInBlackPalace(Vec2 vec) {
        return 3 <= vec.x && vec.x <= 5 && 7 <= vec.y && vec.y <= 9;
    }

    /**
     * @return 是否在红方
     */
    private boolean isInRed(Vec2 vec) {
        return 0 <= vec.x && vec.x <= 8 && 0 <= vec.y && vec.y <= 4;
    }

    /**
     * @return 是否在黑方
     */
    private boolean isInBlack(Vec2 vec) {
        return 0 <= vec.x && vec.x <= 8 && 5 <= vec.y && vec.y <= 9;
    }

    /**
     * @return 是否在棋盘内
     */
    private boolean isInside(Vec2 vec) {
        return this.isInRed(vec) || this.isInBlack(vec);
    }

    /**
     * @return tar 是否被己方棋子占用，己方是指 src 处的棋子所在阵营
     */
    private boolean isOccupiedByOneself(Board board, Vec2 src, Vec2 tar) {
        return board.getPiece(tar.x, tar.y) != null && board.getPiece(src.x, src.y).getOwner() == board.getPiece(tar.x, tar.y).getOwner();
    }

    /**
     * 如果 tar 是合法位置，且 tar 处没有我方棋子，那么向 lis 中放入 tar
     *
     * @param lis   坐标列表
     * @param board 棋盘
     * @param src   初始位置
     * @param tar   目标位置
     * @return 是否成功放入
     */
    private boolean addIfNoOcc(List<Vec2> lis, Board board, Vec2 src, Vec2 tar) {
        if (!this.isInside(tar)) return false;
        if (this.isOccupiedByOneself(board, src, tar)) return false;
        lis.add(tar);
        return true;
    }


}